package com.gitee.feizns.quickstart.web.ex;

import com.gitee.feizns.dynamic.Strings;
import com.gitee.feizns.dynamic.reflect.Classes;
import com.gitee.feizns.quickstart.domain.vo.Ret;
import com.gitee.feizns.quickstart.web.WebUtils;
import com.gitee.feizns.quickstart.web.ex.annotation.ErrorCodeMapper;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 全局异常解析器
 * <p>
 *     因为Spring 默认的异常处理器不支持在异常链中匹配异常处理器<br/>
 *     这里对Spring抛出的异常匹配到我们自定义的异常处理器{@link ExceptionHandler}
 * </p>
 * @see RestControllerAdvice
 * @see org.springframework.web.bind.annotation.ExceptionHandler
 * @author feizns
 * @since 2019/6/13
 */
@Slf4j
@RestControllerAdvice
@NoArgsConstructor
public class GlobalRestfulExceptionAdvice {

    /**
     * 是否是调试模式
     * <p>
     *     调试模式 将打印 Throwable.printStackTrace().
     * </p>
     * @see GlobalRestfulExceptionAdvice#exceptionHandle(Exception)
     * @see GlobalRestfulExceptionAdvice#debugLogging(Throwable)
     */
    private boolean debug;

    /**
     * 声明是否调试模式
     * @param debug 是否调试模式
     */
    public GlobalRestfulExceptionAdvice(boolean debug) {
        this.debug = debug;
    }

    /**
     * 扫描异常栈的形式来匹配异常，配置异常栈中存在的异常地对应解决方案
     */
    private final Map<Class<? extends Throwable>, ExceptionHandler<?>> customizeExceptionHandlerMapping = new LinkedHashMap<>();

    /**
     * 针对要处理的异常进行扫描
     * 在整个异常栈中查找有异常处理规则的异常，并按自定义规则进行处理
     * @see ExceptionHandler 处理自定义规则
     * @param e 异常实例
     * @return {@link ResponseEntity}
     */
    @SuppressWarnings("all")
    @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class)
    public ResponseEntity<?> exceptionHandle(Exception e) {
        if ( debug )
            e.printStackTrace();

        //在整个异常链中查找对应的异常处理器
        ResponseEntity<?> handleCauseResponseEntity = handleCause(e);
        //返回自定义异常匹配的响应结果
        if ( handleCauseResponseEntity != null )
            return handleCauseResponseEntity;

        //没有找到自定义异常处理器
        log.debug("Not found CustomizeExceptionHandler for => " + e.getClass());

        //记录日志
        debugLogging(e);

        //查找异常码映射注解
        ErrorCodeMapper errorCodeMapper = findExceptionMapper(e);
        if ( errorCodeMapper != null )
            return wrap(errorCodeMapper);

        //默认响应
        return defaultResponse(e);
    }

    /**
     * 处理原因
     * @param throwable 异常
     */
    @SuppressWarnings("unchecked")
    private ResponseEntity<?> handleCause(Throwable throwable) {
        ResponseEntity<?> handleCauseResponseEntity = null;

        //优先匹配异常原因类型
        Throwable cause = throwable.getCause();
        if ( cause != null )
            handleCauseResponseEntity = handleCause(cause);
        //返回子原因匹配的异常响应结果
        if ( handleCauseResponseEntity != null )
            return handleCauseResponseEntity;

        //匹配当前异常类型
        Class<Throwable> causeClass = (Class<Throwable>) throwable.getClass();
        //匹配自定义的异常处理器
        ExceptionHandler<Throwable> handler = matchExceptionHandler(causeClass);
        if ( handler != null ) {
            if ( log.isDebugEnabled() )
                log.debug("Found ExceptionHandler for => {}", handler.getClass());
            //记录日志
            debugLogging(throwable);
            //转发异常给自定义异常处理器
            return handler.handle(throwable);
        }
        return null;
    }

    /**
     * 给自定义的异常
     * @param ex 异常类型
     * @param handler {@link ExceptionHandler}
     */
    public void registryCustomizeExceptionHandler(Class<? extends Throwable> ex, ExceptionHandler<?> handler) {
        customizeExceptionHandlerMapping.put(ex, handler);
    }

    /**
     * 从注册的自定义异常解析处理器中查找匹配合适的异常处理器
     * 从给定的异常类的继承体系中查找是否有合适的。
     * @param ex {@link Class}
     * @return {@link ExceptionHandler}
     */

    @SuppressWarnings("unchecked")
    public <T extends Throwable> ExceptionHandler<T> matchExceptionHandler(Class<T> ex) {
        Deque<Class<?>> classes = Classes.of(ex);
        for (Class<?> type : classes) {
            ExceptionHandler<Throwable> handler = (ExceptionHandler<Throwable>) customizeExceptionHandlerMapping.get(type);
            if (handler != null)
                return (ExceptionHandler<T>) handler;
        }
        return null;
    }

    /**
     * 默认没找到异常解析器的情况下，
     * 会用服务器内部错误来应答请求
     * @param e {@link Throwable}
     * @return {@link ResponseEntity}
     */
    public ResponseEntity<?> defaultResponse(Throwable e) {
        String message = e.getMessage();

        //默认消息：UnLoginException -> UN_LOGIN_EXCEPTION
        if ( message == null )
            message = Strings.toSnakeCase(e.getClass().getSimpleName()).toUpperCase();

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Ret.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message));
    }

    /**
     * 查找错误码映射
     * @return {@link ErrorCodeMapper}
     */
    public static ErrorCodeMapper findExceptionMapper(Throwable e) {
        if ( e != null ) {
            //先查找子原因异常映射
            ErrorCodeMapper errorCodeMapper = findExceptionMapper(e.getCause());
            //在子原因异常中没找到异常映射（在当前异常中查找）
            if ( errorCodeMapper != null )
                return errorCodeMapper;
            return e.getClass().getAnnotation(ErrorCodeMapper.class);
        }
        return null;
    }

    /**
     * 包装错误信息
     * @param errorCodeMapper {@link ErrorCodeMapper}
     * @return {@link ResponseEntity}<{@link Ret}<{@link Object}>>
     */
    public static ResponseEntity<Ret<Object>> wrap(ErrorCodeMapper errorCodeMapper) {
        return ResponseEntity.status(errorCodeMapper.status())
                .body(Ret.error(errorCodeMapper.code(), errorCodeMapper.value()));
    }

    /**
     * 记录日志
     * @param cause {@link Throwable}
     */
    private static void debugLogging(Throwable cause) {
        HttpServletRequest req = WebUtils.getReq();
        //打印异常消息
        if ( log.isDebugEnabled() ) {
            log.debug("[{} : {}] IP: {} ==> MESSAGE [ {} ] EXCEPTION[ {} ] ",
                    req.getMethod(),
                    WebUtils.getBasePath(),
                    WebUtils.getIpAddress(),
                    cause.getMessage(),
                    cause.getClass());
        }
    }

}
